//#include "internal.h"
#include <stdio.h>
#include <malloc.h>

#include "coid/comm/binstream/filestream.h"
#include "teximage.h"


#define GLFW_ORIGIN_UL_BIT        0x00000002

//************************************************************************
//****            GLFW internal functions & declarations              ****
//************************************************************************

//========================================================================
// TGA file header information
//========================================================================

typedef struct {
    int idlen;                 // 1 byte
    int cmaptype;              // 1 byte
    int imagetype;             // 1 byte
    int cmapfirstidx;          // 2 bytes
    int cmaplen;               // 2 bytes
    int cmapentrysize;         // 1 byte
    int xorigin;               // 2 bytes
    int yorigin;               // 2 bytes
    int width;                 // 2 bytes
    int height;                // 2 bytes
    int bitsperpixel;          // 1 byte
    int imageinfo;             // 1 byte
    int _alphabits;            // (derived from imageinfo)
    int _origin;               // (derived from imageinfo)
} _tga_header_t;

#define _TGA_CMAPTYPE_NONE      0
#define _TGA_CMAPTYPE_PRESENT   1

#define _TGA_IMAGETYPE_NONE     0
#define _TGA_IMAGETYPE_CMAP     1
#define _TGA_IMAGETYPE_TC       2
#define _TGA_IMAGETYPE_GRAY     3
#define _TGA_IMAGETYPE_CMAP_RLE 9
#define _TGA_IMAGETYPE_TC_RLE   10
#define _TGA_IMAGETYPE_GRAY_RLE 11

#define _TGA_IMAGEINFO_ALPHA_MASK   0x0f
#define _TGA_IMAGEINFO_ALPHA_SHIFT  0
#define _TGA_IMAGEINFO_ORIGIN_MASK  0x30
#define _TGA_IMAGEINFO_ORIGIN_SHIFT 4

#define _TGA_ORIGIN_BL 0
#define _TGA_ORIGIN_BR 1
#define _TGA_ORIGIN_UL 2
#define _TGA_ORIGIN_UR 3


#define PREMULTIPLIED_ALPHA

//========================================================================
// _glfwReadTGAHeader() - Read TGA file header (and check that it is
// valid)
//========================================================================

static int _glfwReadTGAHeader( FILE *f, _tga_header_t *h )
{
    unsigned char buf[ 18 ];
    int pos;

    // Read TGA file header from file
    pos = ftell( f );
    fread( buf, 18, 1, f );

    // Interpret header (endian independent parsing)
    h->idlen         = (int) buf[0];
    h->cmaptype      = (int) buf[1];
    h->imagetype     = (int) buf[2];
    h->cmapfirstidx  = (int) buf[3] | (((int) buf[4]) << 8);
    h->cmaplen       = (int) buf[5] | (((int) buf[6]) << 8);
    h->cmapentrysize = (int) buf[7];
    h->xorigin       = (int) buf[8] | (((int) buf[9]) << 8);
    h->yorigin       = (int) buf[10] | (((int) buf[11]) << 8);
    h->width         = (int) buf[12] | (((int) buf[13]) << 8);
    h->height        = (int) buf[14] | (((int) buf[15]) << 8);
    h->bitsperpixel  = (int) buf[16];
    h->imageinfo     = (int) buf[17];

    // Extract alphabits and origin information
    h->_alphabits = (int) (h->imageinfo & _TGA_IMAGEINFO_ALPHA_MASK) >>
                     _TGA_IMAGEINFO_ALPHA_SHIFT;
    h->_origin    = (int) (h->imageinfo & _TGA_IMAGEINFO_ORIGIN_MASK) >>
                     _TGA_IMAGEINFO_ORIGIN_SHIFT;

    // Validate TGA header (is this a TGA file?)
    if( (h->cmaptype == 0 || h->cmaptype == 1) &&
        ((h->imagetype >= 1 && h->imagetype <= 3) ||
         (h->imagetype >= 9 && h->imagetype <= 11)) &&
         (h->bitsperpixel == 8 || h->bitsperpixel == 24 ||
          h->bitsperpixel == 32) )
    {
        // Skip the ID field
        fseek( f, h->idlen, SEEK_CUR );

        // Indicate that the TGA header was valid
        return 1;
    }
    else
    {
        // Restore file position
        fseek( f, pos, SEEK_SET );

        // Indicate that the TGA header was invalid
        return 0;
    }
}


//========================================================================
// _glfwReadTGA_RLE() - Read Run-Length Encoded data
//========================================================================

static void _glfwReadTGA_RLE( uint32* buf, int size, int bpp,
    FILE *f )
{
    unsigned char pixel[4];

    // Dummy check
    if( bpp > 4 )
    {
        return;
    }

    while( size > 0 )
    {
        // Get repetition count
        int repcount = (unsigned int) fgetc( f );
        int bytes = ((repcount & 127) + 1) * bpp;
        if( size < bytes )
        {
            bytes = size;
        }

        // Run-Length packet?
        if( repcount & 128 )
        {
            fread( pixel, bpp, 1, f );
            uint32 c = *(uint32*)pixel;
            if(bpp<4)
                c |= 0xff000000;
#ifdef PREMULTIPLIED_ALPHA
            else
                c = scale_packed32_rgb( c>>24, c ); //premultiplied alpha
#endif
            
            for( uint n = (repcount & 127) + 1; n>0; --n )
                *buf++ = c;
        }
        else if(bpp<4)
        {
            for( int n = 0; n < bytes; n+=bpp )
            {
                fread( pixel, bpp, 1, f );
                uint32 c = *(uint32*)pixel;
                c |= 0xff000000;
                *buf++ = c;
            }
        }
        else
        {
            // It's a Raw packet
            for( int n = 0; n < bytes; n+=bpp )
            {
                fread( pixel, bpp, 1, f );
                uint32 c = *(uint32*)pixel;
#ifdef PREMULTIPLIED_ALPHA
                *buf++ = scale_packed32_rgb( c>>24, c ); //premultiplied alpha
#else
                *buf++ = c;
#endif
            }
        }

        size -= bytes;
    }
}


//========================================================================
// _glfwReadTGA() - Read a TGA image from a file
//========================================================================

int TexImage::_glfwReadTGA( FILE *f, TexImage* img, int flags )
{
    _tga_header_t h;
    //dynarray<uint8> pix;
    int cmapsize, pixsize;
    int bpp, m, n, swapx, swapy;

    // Read TGA header
    if( !_glfwReadTGAHeader( f, &h ) )
    {
        return 0;
    }

    // Is there a colormap?
    cmapsize = (h.cmaptype == _TGA_CMAPTYPE_PRESENT ? 1 : 0) * h.cmaplen *
               ((h.cmapentrysize+7) / 8);
    if( cmapsize > 0 )
        return 0;   //not supporting the colormaps

    // Size of pixel data
    pixsize = h.width * h.height * ((h.bitsperpixel + 7) / 8);

    // Bytes per pixel (pixel data - unexpanded)
    bpp = (h.bitsperpixel + 7) / 8;
    if( bpp != 3  &&  bpp != 4 )
        return 0;

    // Allocate memory for pixel data
    img->create( h.width, h.height, 32, TexImage::sw_surfaces );
    //pix.need( h.width * h.height * 4 );

    uint32* pix = (uint32*)img->lock();

    // Read pixel data from file
    if( h.imagetype >= _TGA_IMAGETYPE_CMAP_RLE )
    {
        _glfwReadTGA_RLE( pix, pixsize, bpp, f );
    }
    else
    {
        uchar pixel[4];
        uint32* pixv = pix;

        for( n = 0; n < pixsize; n+=bpp )
        {
            fread( pixel, bpp, 1, f );
            uint32 c = *(uint32*)pixel;
            if(bpp<4)
                c |= 0xff000000;
#ifdef PREMULTIPLIED_ALPHA
            else
                c = scale_packed32_rgb( c>>24, c ); //premultiplied alpha
#endif
            *pixv++ = c;
        }
    }

    // If the image origin is not what we want, re-arrange the pixels
    switch( h._origin )
    {
    default:
    case _TGA_ORIGIN_UL:
        swapx = 0;
        swapy = 1;
        break;

    case _TGA_ORIGIN_BL:
        swapx = 0;
        swapy = 0;
        break;

    case _TGA_ORIGIN_UR:
        swapx = 1;
        swapy = 1;
        break;

    case _TGA_ORIGIN_BR:
        swapx = 1;
        swapy = 0;
        break;
    }
    if( (swapy && !(flags & GLFW_ORIGIN_UL_BIT)) ||
        (!swapy && (flags & GLFW_ORIGIN_UL_BIT)) )
    {
        uint32* src = pix;
        uint32* dst = pix + (h.height-1) * h.width;
        for( n = 0; n < h.height/2; ++n )
        {
            for( m = 0; m < h.width ; ++m )
            {
                uint32 t = *src;
                *src++ = *dst;
                *dst++ = t;
            }
            dst -= 2*h.width;
        }
    }
    if( swapx )
    {
        uint32* src = pix;
        uint32* dst = pix + h.width-1;
        for( n = 0; n < h.height; ++n )
        {
            for( m = 0; m < h.width/2 ; ++m )
            {
                uint32 t = *src;
                *src++ = *dst;
                *dst-- = t;
            }
            src += (h.width+1)/2;
            dst += (3*h.width+1)/2;
        }
    }

    img->unlock();

    return 1;
}

////////////////////////////////////////////////////////////////////////////////
opcd TexImage::readTGA( const char* name )
{
    FILE* fp;
    fp = fopen( name, "rb" );
    if(!fp) return ersNOT_FOUND;

    int b = _glfwReadTGA( fp, this, GLFW_ORIGIN_UL_BIT );
    fclose(fp);

    return b  ?  0  :  ersFAILED;
}

////////////////////////////////////////////////////////////////////////////////
opcd TexImage::readTGA( const char* name, uint32 colpri, uint32 colsec )
{
    FILE* fp;
    fp = fopen( name, "rb" );
    if(!fp) return ersNOT_FOUND;

    TexImage tmp;

    if( _glfwReadTGA( fp, &tmp, GLFW_ORIGIN_UL_BIT ) )
    {
        fclose(fp);

        colorize_image( tmp, colpri, colsec );

        return 0;
    }

    fclose(fp);
    return ersFAILED;
}

////////////////////////////////////////////////////////////////////////////////
opcd TexImage::readRAW( const token& name, uint bpp )
{
    bifstream bin;

    opcd e = bin.open(name);
    if(e)  return e;

    ushort bppf;
    bin >> bppf;

    if( bpp != 0  &&  bppf != bpp )
        return ersINVALID_TYPE;

    int w,h;
    bin >> w >> h;

    e = create( w, h, bppf, false );
    if(e)  return e;

    uchar* p = (uchar*)lock();
    uint bypp = align_to_chunks_pow2(bpp,3);

    if( _surf->pitch == w * bypp ) {
        uint s = w*h*bypp;
        e = bin.read_raw( p, s );
    }
    else
    {
        for( int i=0; i<h; ++i, p+=_surf->pitch )
        {
            uint s = w*bypp;
            e = bin.read_raw( p, s );
            if(e)
                break;
        }
    }

    unlock();

    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd TexImage::writeRAW( const token& name )
{
    bofstream bin;

    opcd e = bin.open(name);
    if(e) return e;

    int w = width();
    int h = height();
    bin << _bpp << w << h;

    uchar* p = (uchar*)lock();
    uint bypp = align_to_chunks_pow2((uint)_bpp,3);

    if( _surf->pitch == w * bypp ) {
        uint s = w*h*bypp;
        e = bin.write_raw( p, s );
    }
    else
    {
        for( int i=0; i<h; ++i, p+=_surf->pitch )
        {
            uint s = w*bypp;
            e = bin.write_raw( p, s );
            if(e)
                break;
        }
    }

    unlock();

    return e;
}
